#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <errno.h>
#include <functional>


#include<sys/shm.h>
namespace shm::publish {

#define SHM_SIZE 10485760  //10M

    typedef union semun
    {
        int val;
    }semun;

/********************************** Publisher ******************************************/

    class Publisher
    {
    private:
        /* data */
        int sem_init(int key);
        void sem_p(int id,int sem);
        void sem_v(int id,int sem);
        void sem_del(int id);
        int fd;
        unsigned char* shm_data;
        int semid;
        const char* _name;
    public:
        Publisher(/* args */const char* name);
        int publish(unsigned char* data, int size);
        ~Publisher();
    };

    
    Publisher::Publisher(const char* name)
    :_name(name)
    {
        fd = shm_open(name, O_CREAT | O_RDWR, 0777);
        if(fd < 0)
        {
            printf("shm_open error!!!\n");
            return;
        }
        ftruncate(fd, SHM_SIZE);
        shm_data = (unsigned char*)mmap(NULL, SHM_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        if (!shm_data) 
        {
            printf("mmap failed\n");
            close(fd);
        }

        semid=sem_init(120);
    }

    int Publisher::publish(unsigned char* data, int size)
    {   
		sem_p(semid,0);
        // printf("size:%d\n");
        if (data == NULL)
        {
            /* code */
            printf("data is null\n");
            return -1;
        }
        
        memcpy(shm_data, data, size);

        // *shm_data = 0x12;
		sem_v(semid,0);
    }
    

    int Publisher::sem_init(int key)
    {
        int semid;
        semid=semget((key_t)key,0,0666);
        if(semid==-1)
        {
            semid=semget((key_t)key,1,0666 | IPC_CREAT);
    
            semun mu;
            mu.val=1;
    
            semctl(semid,0,SETVAL,mu);
        }
        return semid;
    }
 
    void Publisher::sem_p(int id,int sem)
    {
        struct sembuf buff;
        buff.sem_num=sem;
        buff.sem_op=-1;
        buff.sem_flg=SEM_UNDO;
        semop(id,&buff,1);
    }
    
    void Publisher::sem_v(int id,int sem)
    {
        struct sembuf buff;
        buff.sem_num=sem;
        buff.sem_op=1;
        buff.sem_flg=SEM_UNDO;
        semop(id,&buff,1);
    }
    
    void Publisher::sem_del(int id)
    {
        semctl(id,0,IPC_RMID);
    }

    Publisher::~Publisher()
    {
        printf("Publisher out\n");
        munmap(shm_data, SHM_SIZE);
        shm_unlink(_name);
    }

/********************************** Memblock ******************************************/

struct Memblock {
  void *ptr;
  size_t size;
  bool free;

  Memblock() : ptr(nullptr), size(0), free(true) {}
  Memblock(void *ptr, size_t size) : ptr(ptr), size(size), free(true) {}

  void no_delete() { free = false; }

  bool is_empty() const { return ptr == nullptr && size == 0; }
};

/********************************** Subscriber ******************************************/
    class Subscriber
    {
    private:
        /* data */
        int sem_init(int key);
        void sem_p(int id,int sem);
        void sem_v(int id,int sem);
        void sem_del(int id);
        std::function<void(Memblock *)> callback_;
        int fd;
        unsigned char* shm_data;
        int semid;
    public:
        Subscriber(/* args */const char* name, std::function<void(Memblock *)> callback);
        void spin_once();
        ~Subscriber();
    };
    
    Subscriber::Subscriber(/* args */const char* name, std::function<void(Memblock *)> callback)
    :callback_(std::move(callback))
    {
        fd = shm_open(name, O_RDWR, 0777);
        if(fd < 0)
        {
                printf("error open shm object\n");
        }
        shm_data = (unsigned char*)mmap(NULL, SHM_SIZE, PROT_READ, MAP_SHARED, fd, 0);
        if (!shm_data) 
        {
                printf("mmap failed!\n");
                close(fd);
        }
        semid=sem_init(120);
    }

    void Subscriber::spin_once()
    {
        sem_p(semid,0);
        Memblock mem;
        mem.ptr = shm_data;
        callback_(&mem);
        sem_v(semid,0);
    }

    int Subscriber::sem_init(int key)
    {
        int semid;
        semid=semget((key_t)key,0,0666);
        if(semid==-1)
        {
            semid=semget((key_t)key,1,0666 | IPC_CREAT);
    
            semun mu;
            mu.val=1;
    
            semctl(semid,0,SETVAL,mu);
        }
        return semid;
    }
 
    void Subscriber::sem_p(int id,int sem)
    {
        struct sembuf buff;
        buff.sem_num=sem;
        buff.sem_op=-1;
        buff.sem_flg=SEM_UNDO;
        semop(id,&buff,1);
    }
    
    void Subscriber::sem_v(int id,int sem)
    {
        struct sembuf buff;
        buff.sem_num=sem;
        buff.sem_op=1;
        buff.sem_flg=SEM_UNDO;
        semop(id,&buff,1);
    }
    
    void Subscriber::sem_del(int id)
    {
        semctl(id,0,IPC_RMID);
    }

    Subscriber::~Subscriber()
    {
        munmap(shm_data,SHM_SIZE);
        close(fd);
    }
    
}